'''
    Copyright (C) 2007  Brian Will

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

'''


import wx
import sys
import os
import __builtin__
import time

from styledTextControl import PigeonSTC
from pigeonCompiler import compileModule



class Pigeon(wx.App):
    def OnInit(self):
        frame = TopFrame(None, "Pigeon - new.pig")
        self.SetTopWindow(frame)
        frame.Show(True)
        return True
    

ID_RUN = wx.NewId()
ID_OPEN = wx.NewId()
ID_SAVE = wx.NewId()
ID_NEW = wx.NewId()


class TopFrame(wx.Frame):

    """
    This is MyFrame.  It just shows a few controls on a wxPanel,
    and has a simple menu.
    """
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, -1, title, 
                          pos=(150, 150), size=(1024, 768))
        
        self.Bind(wx.EVT_MENU, self.OnTimeToClose, id=wx.ID_EXIT)
        self.Bind(wx.EVT_MENU, self.OnRun, id=ID_RUN)
        self.Bind(wx.EVT_MENU, self.OnNew, id=ID_NEW)
        self.Bind(wx.EVT_MENU, self.OnSave, id=ID_SAVE)
        self.Bind(wx.EVT_MENU, self.OnOpen, id=ID_OPEN)
        
        self.SetIcon(wx.Icon('pigeon.ico', wx.BITMAP_TYPE_ICO))
        
        menuBar = wx.MenuBar()
        
        menu = wx.Menu()
        menu.Append(ID_NEW, "&New\tCtrl+N", "Start a new module")
        menu.Append(ID_OPEN, "&Open\tCtrl+O", "Open a module")
        menu.Append(ID_SAVE, "&Save\tCtrl+S", "Save the current module")
        menu.Append(wx.ID_EXIT, "E&xit\tAlt-F4", "Exit Pigeon")
        menuBar.Append(menu, "&File")
        
        menu = wx.Menu()
        menu.Append(ID_RUN, "&Run\tF9", 'Run the currently open module')
        menuBar.Append(menu, "&Run")
        
        self.SetMenuBar(menuBar)

        self.CreateStatusBar()

        panel = wx.Panel(self)

        self.textEditor = textEditor = PigeonSTC(panel, -1)
        textEditor.EmptyUndoBuffer()
        
        self.pigeonmoduleDir = os.getcwd() + '/modules'
        self.currentFilePath = self.pigeonmoduleDir + '/new.pig'
        self.currentFileName = 'new.pig'
        
        
        # defaults to text of pigeonmodules/new.pig
        if os.path.exists(self.currentFilePath):
            file = open(self.currentFilePath, 'r')
            text = file.read()
            file.close()
            textEditor.SetText(text)
        else:
            file = open(self.currentFilePath, 'w')
            file.write('')
            file.close()
            textEditor.SetText('')
        self.currentFileIsSaved = False
            
        
        self.outputLog = outputLog = wx.TextCtrl(panel, -1, 
            '', 
            size=(200, 100), 
            style=wx.TE_MULTILINE|wx.TE_RICH|wx.HSCROLL|wx.TE_READONLY
        )
        self.clearConsole()
                
        sys.stdout = StdOutRedirect(outputLog)
        sys.stderr = StdErrRedirect(outputLog)
                
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(textEditor, 3, wx.EXPAND)
        sizer.Add(outputLog, 1, wx.EXPAND)

        panel.SetSizer(sizer)
        panel.Layout()
        
    
    def clearConsole(self):
        self.outputLog.Clear()
        t = time.ctime()
        self.outputLog.AppendText(t + '\nPrint ouput appears here:\n')  
    
      
    def messageBox(self, message, title=''):
        dialog = wx.MessageDialog(
            self,
            message,
            title,
            wx.OK | wx.ICON_INFORMATION
        )
        dialog.ShowModal()
        dialog.Destroy()  

        
    def promptToSave(self, message, title=''):
        dialog = wx.MessageDialog(
              self, 
              message,
              title,
              wx.YES_NO | wx.ICON_QUESTION
        )
        if dialog.ShowModal() == wx.ID_YES:
            self.OnSave()                
        
        dialog.Destroy()
        

    def OnTimeToClose(self, evt):
        """Event handler for the button click."""
        if not self.currentFileIsSaved:
            self.promptToSave('Save current file before exiting?')
        self.Close()
        

    def OnRun(self, evt):
        ri = RollbackImporter()
        
        if not self.currentFileIsSaved:
            dialog = wx.MessageDialog(
                  self, 
                  'To run this module, you must save the current file.\nSave and continue?',
                  'Save and continue',
                  wx.YES_NO | wx.ICON_QUESTION
            )
            if dialog.ShowModal() == wx.ID_YES:
                self.OnSave()              
            
            dialog.Destroy()
            
        if self.currentFileIsSaved:
            self.clearConsole()
            try:
                __import__('modules.' + self.currentFileName[:-4]) # strip '.pig' from end
            except Exception, err:
                sys.stderr.write("RUNTIME ERROR: " + err.message + '\n')
        
        ri.rollback()
        

    def OnNew(self, evt=None):
        
        if not self.currentFileIsSaved:
            self.promptToSave('Save current file before continuing?')
        
        self.textEditor.SetText('')
        self.SetTitle('Pigeon - new.pig')
        self.currentFileName = 'new.pig'
        self.currentFilePath = self.pigeonmoduleDir + '/new.pig'
        self.currentFileIsSaved = False


    def OnSave(self, evt=None):
        dialog = wx.FileDialog(
            self, 
            message="Save file as ...", 
            defaultDir=self.pigeonmoduleDir,
            defaultFile=self.currentFileName, 
            wildcard="Pigeon module (*.pig)|*.pig",
            style=wx.SAVE
        )

        if dialog.ShowModal() == wx.ID_OK:
            self.currentFilePath = path = dialog.GetPath()
            dir, fileName = os.path.split(path)
            self.currentFileName = fileName
            
            if os.path.normpath(dir) == os.path.normpath(self.pigeonmoduleDir):
                file = open(path, 'w')
                file.write(self.textEditor.GetText())
                file.close()
                
                try:
                    compileModule(path)
                except (Exception, AssertionError), err:
                    sys.stderr.write("COMPILE ERROR: " + err.message + '\n')
                
                self.SetTitle('Pigeon - ' + fileName)
                self.currentFileIsSaved = True
            else:
                self.messageBox('Module not saved.\n\nYou must save your modules in ' + self.pigeonmoduleDir)
            
        dialog.Destroy()

    
    def OnOpen(self, evt):
        
        if not self.currentFileIsSaved:
            self.promptToSave('Save current file before continuing?')
        
        dialog = wx.FileDialog(
            self, 
            message="Choose a file", 
            defaultDir=self.pigeonmoduleDir, 
            defaultFile="", 
            wildcard="Pigeon module (*.pig)|*.pig", 
            style=wx.OPEN
        )

        if dialog.ShowModal() == wx.ID_OK:
            self.currentFilePath = path = dialog.GetPath()
            self.currentFileName = fileName = os.path.split(path)[1]
            
            file = open(path, 'r')
            
            text = file.read()
            self.textEditor.SetText(text)
            
            self.SetTitle('Pigeon - ' + fileName)
            self.currentFileIsSaved = True
        else:
            sys.stderr.write('Problem with file selection dialog.')
            
        dialog.Destroy()


class StdOutRedirect(object):
    def __init__(self, textCtrl):
        self.textCtrl = textCtrl
    def write(self, text):
        textCtrl = self.textCtrl
        textCtrl.AppendText(text)
        
        # adjust window position (simply showing the last position overshoots by a full window height, so need to correct)
        vertOffset = int(textCtrl.GetSize()[1] * 0.25)  # in pixels?
        pos = textCtrl.GetLastPosition() - vertOffset  
        if pos < 0:
            pos = 0
        
        textCtrl.ShowPosition(pos)


class StdErrRedirect(object):
    def __init__(self, textCtrl):
        self.textCtrl = textCtrl
    def write(self, text):
        textCtrl = self.textCtrl
        textCtrl.SetDefaultStyle(wx.TextAttr(wx.RED))
        textCtrl.AppendText(text)
        textCtrl.SetDefaultStyle(wx.TextAttr(wx.BLACK))
        
        # adjust window position (simply showing the last position overshoots by a full window height, so need to correct)
        vertOffset = int(textCtrl.GetSize()[1] * 0.25)  # in pixels?
        pos = textCtrl.GetLastPosition() - vertOffset  
        if pos < 0:
            pos = 0
        
        textCtrl.ShowPosition(pos)


class RollbackImporter(object):
    '''
        from http://pyunit.sourceforge.net/notes/reloading.html
    '''
    def __init__(self):
        "Creates an instance and installs as the global importer"
        self.previousModules = sys.modules.copy()
        self.realImport = __builtin__.__import__
        __builtin__.__import__ = self._import
        self.newModules = {}
        
    def _import(self, name, globals=None, locals=None, fromlist=[]):
        result = apply(self.realImport, (name, globals, locals, fromlist))
        self.newModules[name] = 1
        return result
        
    def rollback(self):
        for modname in self.newModules.keys():
            if not self.previousModules.has_key(modname):
                # Force reload when modname next imported
                del sys.modules[modname]
        __builtin__.__import__ = self.realImport
        
        
app = Pigeon(redirect=False)
app.MainLoop()
